﻿#region Imports
using System;
using System.Collections.Generic;
using CommonUtilities.Enums;
using CommonUtilities.Extensions;
using CommonUtilities.Properties;
using CommonUtilities.Util;
#endregion

namespace CommonUtilities.Models
{
    /// <summary>
    /// Allows handling of memory in (and/or power of) bytes
    /// <para>(Search for 'MemMgmt' tag to add code whilst on-boarding new byte-type)</para>
    /// </summary>
    public class MemoryMgmt
    {
        #region Declarations
        /// <summary>
        /// Stores value in bytes
        /// </summary>
        private double byt;

        /// <summary>
        /// Stores no. of places to round up for each byte-type
        /// </summary>
        private Dictionary<eByteValueType, int> ByteRoundUpPlaces = new Dictionary<eByteValueType, int>();
        #endregion

        #region Properties
        /// <summary>
        /// Significant byte-type
        /// </summary>
        public eByteValueType SignificantByteType { get; set; }

        /// <summary>
        /// Stores byte-type in Bytes (B)
        /// </summary>
        private double Byt
        {
            get { return CalculateByteTypeValue(eByteValueType.B); }
        }

        /// <summary>
        /// Stores byte-type in KiloBytes (KB)
        /// </summary>
        private double KByt
        {
            get { return CalculateByteTypeValue(eByteValueType.KB); }
        }

        /// <summary>
        /// Stores byte-type in MegaBytes (MB)
        /// </summary>
        private double MByt
        {
            get { return CalculateByteTypeValue(eByteValueType.MB); }
        }

        /// <summary>
        /// Stores byte-type in GigaBytes (GB)
        /// </summary>
        private double GByt
        {
            get { return CalculateByteTypeValue(eByteValueType.GB); }
        }

        /// <summary>
        /// Stores byte-type in TeraBytes (TB)
        /// </summary>
        private double TByt
        {
            get { return CalculateByteTypeValue(eByteValueType.TB); }
        }

        // MemMgmt: Add <new private property to hold converted value> here for new byte-type

        /// <summary>
        /// Backing field for UpgradeThresholdValue property
        /// </summary>
        private int thresholdValue;
        /// <summary>
        /// Threshold to display value in the upper byte-type
        /// </summary>
        public int UpgradeThresholdValue
        {
            get { return thresholdValue; }
            set
            {
                if (Utilities.CompareValues(value, 1, eComparers.LesserThan))
                    throw new InvalidOperationException("A value less than 1 is not permitted. Please select in the range: 1-1023");
                if (Utilities.CompareValues(value, 1023, eComparers.GreaterThan))
                    throw new InvalidOperationException("A value greater than 1023 is not permitted. Please select in the range: 1-1023");

                // Restore old value in case of exception
                thresholdValue = value;
            }
        }

        /// <summary>
        /// Backing field for PracticalSize property
        /// </summary>
        private string practicalSize;
        /// <summary>
        /// Specifes significant size in a selected byte-type according to no. of bytes
        /// </summary>
        public string PracticalSize
        {
            get
            {
                // RKD: develop generic code
                if (Byt == 0) SignificantByteType = eByteValueType.B; // 0 B

                // MemMgmt: Add <significant value-type check according to ranking> below here for new byte-type

                else if (GByt >= UpgradeThresholdValue) SignificantByteType = eByteValueType.TB;
                else if (GByt >= 1 && GByt < UpgradeThresholdValue) SignificantByteType = eByteValueType.GB; // 1+ GB --> GB

                else if (MByt >= UpgradeThresholdValue && GByt < 1) SignificantByteType = eByteValueType.GB; // 500-1024 MB --> GB
                else if (MByt >= 1 && MByt < UpgradeThresholdValue) SignificantByteType = eByteValueType.MB; // 1-500 MB --> MB

                else if (KByt >= UpgradeThresholdValue && MByt < 1) SignificantByteType = eByteValueType.MB; // 500-1024 KB --> MB
                else if (KByt >= 1 && KByt < UpgradeThresholdValue) SignificantByteType = eByteValueType.KB; // 1-500 KB --> KB

                else if (Byt >= UpgradeThresholdValue && Byt < 1) SignificantByteType = eByteValueType.KB; // 500-1024 B --> KB
                else if (Byt >= 1 && KByt < UpgradeThresholdValue) SignificantByteType = eByteValueType.B; // 1-500 B --> B

                practicalSize = CalculateByteTypeValue(SignificantByteType, true).ToString(SignificantByteType);
                return practicalSize;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes instance of MemoryMgmt against specified value and byte-type
        /// </summary>
        /// <param name="type">Byte-type of value specified</param>
        /// <param name="value">Value in terms of byte-type</param>
        public MemoryMgmt(eByteValueType type, double value)
        {
            UpgradeThresholdValue = 500;

            ByteRoundUpPlaces.AddToDictionary(eByteValueType.B, 0);
            ByteRoundUpPlaces.AddToDictionary(eByteValueType.KB, 0);
            ByteRoundUpPlaces.AddToDictionary(eByteValueType.MB, 0);
            ByteRoundUpPlaces.AddToDictionary(eByteValueType.GB, 2);
            ByteRoundUpPlaces.AddToDictionary(eByteValueType.TB, 3);
            // MemMgmt: Add <round-up places> here for new byte-type

            // MemMgmt: Add <new case for switch statement> here for new byte-type
            switch (type)
            {
                case eByteValueType.B:
                case eByteValueType.KB:
                case eByteValueType.MB:
                case eByteValueType.GB:
                case eByteValueType.TB:
                    byt = value * Math.Pow(1024, GetPowerDifference(type, eByteValueType.B));
                    break;

                default:
                    throw new InvalidOperationException(Resources.MemMgmt_ImproperValueType);
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the difference in power for specified byte-types
        /// <para><code>result := from - to</code></para>
        /// </summary>
        /// <param name="from">Power of byte-type to convert from</param>
        /// <param name="to">Power of byte-type to convert to</param>
        /// <returns></returns>
        private int GetPowerDifference(eByteValueType from, eByteValueType to)
        {
            return from - to;
        }

        /// <summary>
        /// Calculates byte-type value
        /// </summary>
        /// <param name="byteValueType">Byte-type</param>
        /// <param name="isPracticalSizeCalculation">TRUE: Denotes practical size calculation (displays with zero decimal places)
        /// <para>FALSE: Uses decimal places as per byte-type</para></param>
        /// <returns></returns>
        private double CalculateByteTypeValue(eByteValueType byteValueType, bool isPracticalSizeCalculation = false)
        {
            int highBytePlaces = (Convert.ToInt32(byteValueType) >= eByteValueType.GB.ChangeType<int>() ? (byteValueType.ChangeType<int>() - eByteValueType.MB.ChangeType<int>()) : 0);
            int roundUpPlaces = isPracticalSizeCalculation ? highBytePlaces : ByteRoundUpPlaces[byteValueType];
            int powerDiff = GetPowerDifference(eByteValueType.B, byteValueType);

            return Math.Round(byt * Math.Pow(1024, powerDiff), roundUpPlaces);
        }

        /// <summary>
        /// Creates summary for specified byte-type
        /// (skips if specified byte-type is less than significant byte-type)
        /// </summary>
        /// <param name="byteValueType">Byte-type to summarize size for</param>
        /// <returns></returns>
        public string SummaryByteTypeValue(eByteValueType byteValueType)
        {
            if (byteValueType > SignificantByteType) return "";

            // MemMgmt: Add <new case to convert to summary acc. specified byte-type> below here for new byte-type
            switch (byteValueType)
            {
                case eByteValueType.KB:
                    return "\n     " + this.KByt.ToString(byteValueType);
                case eByteValueType.MB:
                    return "\n     " + this.MByt.ToString(byteValueType);
                case eByteValueType.GB:
                    return "\n     " + this.GByt.ToString(byteValueType);
                case eByteValueType.TB:
                    return "\n     " + this.TByt.ToString(byteValueType);
                case eByteValueType.B:
                default:
                    return "\n     " + this.Byt.ToString(byteValueType);
            }
        }
        #endregion
    }
}
